package cn.fz.demo02.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.swing.*;

/**
 * 1. 简介与概念
 * Spring Security 提供了基于身份验证（Authentication）和授权（Authorization）的安全模型。
 * 身份验证是验证用户身份的过程，而授权则是决定用户是否有权访问资源。
 *
 * 核心组件：
 * SecurityFilterChain：负责定义 HTTP 请求的安全过滤链。
 * UserDetailsService：用于加载用户信息，提供身份验证。
 * PasswordEncoder：处理用户密码的加密与解密
 * 原文链接：https://blog.csdn.net/u014390502/article/details/142965667
 * 原文链接：https://blog.csdn.net/u014390502/article/details/142991973
 *
 *
 * 解释：
 * SecurityFilterChain 定义了所有 HTTP 请求的安全策略。在这里，/ 路径对所有人公开，而其他路径需要用户身份认证。
 * UserDetailsService 提供了用户的详细信息，包括用户名、密码及角色。在这个例子中，我们创建了一个用户名为 “user” 的用户，密码为 “password”（经过加密处理），并分配了 “USER” 角色，如果不配置，系统则会在日志中输出名为 user 的用户对应的密码：Using generated security password: b9fe7857-97a3-4db7-9602-9e10db56496d。
 * PasswordEncoder 通过 BCryptPasswordEncoder 实现密码加密，以确保用户密码存储时是安全的。
 * @EnableWebSecurity注解启动 Spring Security 的自动配置，使得应用能够自动集成 Spring Security 提供的安全功能。
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    // 通过构造函数注入自定义UserDetailsService
    private final CustomUserDetailsService userDetailsService;

    public SecurityConfig(CustomUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    /**
     * SecurityFilterChain：负责定义 HTTP 请求的安全过滤链。
     * SecurityFilterChain 定义了所有 HTTP 请求的安全策略。在这里，/ 路径对所有人公开，而其他路径需要用户身份认证。
     *
     * 2.2 代码说明
     * authorizeHttpRequests()：用于定义 URL 路径的访问权限。
     * requestMatchers("/admin/**").hasRole("ADMIN")：指定 /admin/** 下的所有路径都只有 ADMIN 角色的用户可以访问。
     * requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")：允许 USER 和 ADMIN 角色访问 /user/** 下的资源。
     * anyRequest().authenticated()：表示系统中的其他请求都需要用户登录后才可以访问。
     * formLogin() 和 httpBasic()：分别启用了表单登录和基本 HTTP 身份验证。
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
        // .csrf(Customizer.withDefaults())
        // .csrf(csrf->csrf.disable())
        .csrf(csrf->csrf.disable())  // 若某些请求无需 CSRF 保护可以禁用
        .authorizeHttpRequests(auth->auth
            // .requestMatchers("/").permitAll()  // 公开访问
            // 这段配置确保了 /admin/** 路径只能由具有 ADMIN 角色的用户访问，其他路径则需要用户登录后才能访问。
            .requestMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色可以访问 /admin 目录下的资源
            // .requestMatchers("/user/**").hasAnyRole("USER","ADMIN") // USER 和 ADMIN 角色都可以访问 /user 目录下的资源
            .anyRequest().authenticated()  // 其他接口需认证
        )
        // 使其使用自定义的 UserDetailsService 来进行用户验证：
        .userDetailsService(userDetailsService)
        // 启用内容安全策略（CSP）
        // 这种配置通过**内容安全策略（CSP）**来防止外部来源的脚本加载，从而有效减少 XSS 攻击的风险。
        // 输出编码：在服务端，应确保在返回 HTML 内容时，对用户输入的数据进行适当的转义处理，以防止恶意脚本注入。
        // .headers(headers-> headers.contentSecurityPolicy(contentSecurityPolicyConfig ->contentSecurityPolicyConfig.policyDirectives("script-src 'self'")))
        // 5. 安全头部配置
        // HTTP 头部是重要的安全防护部分。Spring Security 提供了很多内建的 HTTP 头部配置来增强安全性：
        // 这些安全头部能够有效地防范一系列的攻击，包括Clickjacking、内容嗅探、以及通过不安全的 HTTP 协议传输敏感数据。
        .headers(headers->headers
            // .xssProtection(Customizer.withDefaults()) // 防范XSS
            // 6. 防范Clickjacking攻击
            // Clickjacking 是通过将网页嵌入到恶意页面的 iframe 中，使得用户误点击，执行意图不明的操作。可以通过设置 X-Frame-Options 来防范这类攻击。
            //     这种配置将只允许页面嵌入自身的 iframe，而拒绝外部页面嵌入。
            // .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // 防范点击劫持
            // .contentTypeOptions(Customizer.withDefaults()) // 禁止内容嗅探
            // .httpStrictTransportSecurity(Customizer.withDefaults()) // 强制 HTTPS 连接
            .contentSecurityPolicy(contentSecurityPolicyConfig -> contentSecurityPolicyConfig.policyDirectives("default-src 'self'"))
        )
        // 除了 Basic 认证，Spring Security 还支持表单登录。通过 formLogin() 方法，可以启用表单认证，也提供自定义的登录页面。
        .formLogin(form->form
            .loginPage("/login")  // 自定义登录页面
            .permitAll()  // 登录页面无需认证
        )
                // 4. 防范暴力破解
                // 暴力破解 是攻击者不断尝试不同的用户名和密码组合，直到找到正确的凭据。为防范暴力破解攻击，可以使用账户锁定策略和限制登录次数。
                // 配置登录失败次数限制：通过设置登录失败后的延时或账户锁定策略，可以显著减少暴力破解的威胁。
                // .formLogin(form->form.failureHandler((request, response, exception)->response.sendError(401)))
        .logout(logout->logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET")))
        .httpBasic(Customizer.withDefaults()); // 使用 HTTP Basic 认证
        // .logout(LogoutConfigurer::permitAll); // 允许注销 等于下面这一句
        // .logout(logout->logout.permitAll());
        // .formLogin(Customizer.withDefaults())  // 使用默认登录
        // .httpBasic(Customizer.withDefaults());  // 使用 HTTP Basic 认证
        return http.build();
    }

    /**
     * UserDetailsService：用于加载用户信息，提供身份验证。
     * UserDetailsService 提供了用户的详细信息，包括用户名、密码及角色。在这个例子中，我们创建了一个用户名为 “user” 的用户，密码为 “password”（经过加密处理），并分配了 “USER” 角色，如果不配置，
     * 系统则会在日志中输出名为 user 的用户对应的密码：Using generated security password: b9fe7857-97a3-4db7-9602-9e10db56496d。
     * @param passwordEncoder
     * @return
     */
    // @Bean
    // public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
    //     InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
    //     // 创建用户
    //     UserDetails user = User.builder()
    //             .username("asan")
    //             .password(passwordEncoder.encode("123456"))
    //             .roles("USER")
    //             .build();
    //     UserDetails admin = User.builder()
    //             .username("admin")
    //             .password(passwordEncoder.encode("123456"))
    //             .roles("ADMIN")
    //             .build();
    //     userDetailsManager.createUser(user);
    //     userDetailsManager.createUser(admin);
    //     return userDetailsManager;
    // }

    /**
     * 3. 密码加密
     * Spring Security 强烈建议使用加密算法对密码进行加密，防止敏感信息泄露。
     * 在 Spring Boot 3 中，BCryptPasswordEncoder 是一种常用的加密方式。它基于 bcrypt 算法，提供了足够的强度和安全性。
     * 3.1. 如何加密用户密码
     * 在 UserDetailsService 中，我们通过 passwordEncoder.encode("password") 对用户密码进行加密。
     * 在身份验证时，Spring Security 会自动使用同样的加密算法进行密码比对。
     * 3.2. 自定义密码加密器
     * 如果需要自定义密码加密算法，可以实现 PasswordEncoder 接口。以下是自定义加密器的简单示例：
     * 在这个示例中，我们为 BCryptPasswordEncoder 提供了加密强度参数，值越大，安全性越高，但加密速度会相对减慢。
     * BCryptPasswordEncoder 的默认实现使用 BCryptPasswordEncoder 的 Javadoc 中提到的强度10。
     * 或者使用 SpringBoot CLI 对密码进行加密：https://www.zhangshengrong.com/p/JKN8L9gN6b/
     * $ spring encodepassword password
     *
     * PasswordEncoder：处理用户密码的加密与解密
     * PasswordEncoder 通过 BCryptPasswordEncoder 实现密码加密，以确保用户密码存储时是安全的。
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        /**
         * 5. 使用 Bcrypt 加密密码
         * 在实际开发中，我们不能将密码以明文形式存储在数据库中。我们可以使用 Spring Security 提供的 BCryptPasswordEncoder 来加密用户密码
         *
         * 在用户注册或更新密码时，使用这个加密器对密码进行加密后再存储：
         * String encodedPassword = passwordEncoder.encode(plainPassword);
         * user.setPassword(encodedPassword);
         * userRepository.save(user);
         */
        // 使用 BCrypt 进行密码加密
        return new BCryptPasswordEncoder();
        // return new BCryptPasswordEncoder(10);  // 设置加密强度
    }
}
